#include <iostream>
#include <map>

#include <QtGui>

#include "invader_army.h"
#include "invader.h"
#include "media.h"

InvaderArmy::InvaderArmy (QWidget *parent)
  : QFrame (parent)
{
  Invader *inv;
  int x = 0;

  _animationTimer = new QTimer (this);
  _animationTimer->setInterval (500);
  connect (_animationTimer, SIGNAL (timeout ()),
           this, SLOT (animationTimerTimeout ()));

  for (int i = 0; i < numInvaders; ++i) {
    if (i < 10) {
      inv = new Invader (MEDIA_DIR INVADER3_GIF, Invader::InvaderOne, this);
      x = i % 10 * 32 + 4;
    } else if (i >= 10 && i < 30) {
      inv = new Invader (MEDIA_DIR INVADER2_GIF, Invader::InvaderTwo, this);
      x = i % 10 * 32 + 1;
    } else {
      inv = new Invader (MEDIA_DIR INVADER1_GIF, Invader::InvaderThree, this);
      x = i % 10 * 32;
    }

    _invaders.insert (std::pair<int, Invader *> (x, inv));
    inv->move (x, i / 10 * 32);
    inv->setMouseTracking (true);
    connect (_animationTimer, SIGNAL (timeout ()),
             inv, SLOT (advanceFrame ()));
    connect (inv, SIGNAL (shoots ()), this, SLOT (invaderShot ()));
  }

  _invadersAlive = numInvaders;

  _newTimerInterval = 0;
}

void InvaderArmy::tryHitInvader (const QRect &rect)
{
  /* Since the largest invader is 24 pixels wide we can limit our search
   * to 24 pixels to the left and right of the test point */
  std::multimap<int, Invader *>::iterator beg = _invaders.lower_bound (
    rect.x () - 24);
  std::multimap<int, Invader *>::iterator end = _invaders.upper_bound (
    rect.x () + 24);

  for (; beg != end; ++beg) {
    if (beg->second->isHidden ())
      continue;

    if (beg->second->geometry ().intersects (rect)) {
      --_invadersAlive;
      beg->second->setVisible (false);
      updateAnimationInterval ();
      emit invaderKilled ((int)beg->second->getType ());
      break;
    }
  }
}

int InvaderArmy::getMinX (int *maxY) const
{
  int minX = width ();

  std::multimap<int, Invader *>::const_iterator beg = _invaders.begin ();
  std::multimap<int, Invader *>::const_iterator end = _invaders.end ();

  for (; beg != end; ++beg) {
    if (beg->second->isHidden ())
      continue;

    if (beg->second->x () < minX) {
      minX = beg->second->x ();
      if (maxY != 0)
        *maxY = beg->second->y () + beg->second->height ();
    }
  }

  return minX;
}

int InvaderArmy::getMaxX (int *maxY) const
{
  int maxX = 0;

  std::multimap<int, Invader *>::const_iterator beg = _invaders.begin ();
  std::multimap<int, Invader *>::const_iterator end = _invaders.end ();

  for (; beg != end; ++beg) {
    if (beg->second->isHidden ())
      continue;

    if (beg->second->x () + beg->second->width () > maxX) {
      maxX = beg->second->x () + beg->second->width ();
      if (maxY != 0)
        *maxY = beg->second->y () + beg->second->height ();
    }
  }

  return maxX;
}

int InvaderArmy::getMinY () const
{
  int minY = height ();

  std::multimap<int, Invader *>::const_iterator beg = _invaders.begin ();
  std::multimap<int, Invader *>::const_iterator end = _invaders.end ();

  for (; beg != end; ++beg) {
    if (beg->second->isHidden ())
      continue;

    if (beg->second->y () < minY)
      minY = beg->second->y ();
  }

  return minY;
}

int InvaderArmy::getMaxY () const
{
  int maxY = 0;

  std::multimap<int, Invader *>::const_iterator beg = _invaders.begin ();
  std::multimap<int, Invader *>::const_iterator end = _invaders.end ();

  for (; beg != end; ++beg) {
    if (beg->second->isHidden ())
      continue;

    if (beg->second->y () + beg->second->height () > maxY)
      maxY = beg->second->y () + beg->second->height ();
  }

  return maxY;
}

QSize InvaderArmy::sizeHint () const
{
  int width = 0;
  int height = 0;

  std::multimap<int, Invader *>::const_iterator beg = _invaders.begin ();
  std::multimap<int, Invader *>::const_iterator end = _invaders.end ();

  for (int tmp; beg != end; ++beg) {
    tmp = beg->second->x () + beg->second->width ();
    if (tmp > width)
      width = tmp;

    tmp = beg->second->y () + beg->second->height ();
    if (tmp > height)
      height = tmp;
  }

  return QSize (width, height);
}

int InvaderArmy::numInvaders = 50;

void InvaderArmy::start ()
{
  _animationTimer->start ();
}

void InvaderArmy::stop ()
{
  _animationTimer->stop ();
}

void InvaderArmy::reset ()
{
  std::multimap<int, Invader *>::iterator beg = _invaders.begin ();
  std::multimap<int, Invader *>::iterator end = _invaders.end ();

  for (; beg != end; ++beg)
    beg->second->setVisible (true);

  _invadersAlive = numInvaders;
  updateAnimationInterval ();
}

void InvaderArmy::pause ()
{
  if (_animationTimer->isActive ())
    _animationTimer->stop ();
  else
    _animationTimer->start ();
}

void InvaderArmy::resizeEvent (QResizeEvent *event)
{
  QSize size;

  std::multimap<int, Invader *>::iterator beg = _invaders.begin ();
  std::multimap<int, Invader *>::iterator end = _invaders.end ();

  for (; beg != end; ++beg) {
    size = beg->second->sizeHint ();
    beg->second->setGeometry (beg->second->x (), beg->second->y (),
                              size.width (), size.height ());
  }
}

void InvaderArmy::moveEvent (QMoveEvent *event)
{
  const QRect &geo = geometry ();

  if (event->oldPos ().y () != geo.y ())
    updateAnimationInterval ();
}

void InvaderArmy::updateAnimationInterval ()
{
  _newTimerInterval = 500 - (y () * 200 / 220
                             + (numInvaders - _invadersAlive) * 5);
}

void InvaderArmy::animationTimerTimeout ()
{
  if (_newTimerInterval != 0) {
    _animationTimer->setInterval (_newTimerInterval);
    _newTimerInterval = 0;
  }

  emit frameChanged ();
}

void InvaderArmy::invaderShot ()
{
  Invader *inv = (Invader *)sender ();

  /* There is a difference of 8 pixels between the widest and narrowest invader
   * so narrow search to 8 pixels to the left and right */
  std::multimap<int, Invader *>::iterator beg = _invaders.lower_bound (
    inv->x () - 8);
  std::multimap<int, Invader *>::iterator end = _invaders.upper_bound (
    inv->x () + 8);

  /* If there is an invader below the one that shot don't and it is visible
   * don't emit the invader shot signal */
  for (int invY = inv->y (); beg != end; ++beg) {
    if (beg->second->isVisible () && beg->second->y () > invY)
      return;
  }

  emit invaderShoots (QPoint (inv->x () + inv->width () / 2 - 1,
                              inv->y ()));
}
